/*
 * $Id: gensrclist.cc,v 1.8 2003/01/30 17:18:21 niemeyer Exp $
 */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <alloca.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <rpm/rpmlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <assert.h>

#include <map>
#include <list>
#include <vector>
#include <iostream>
#include <fstream>

#include <apt-pkg/error.h>
#include <apt-pkg/tagfile.h>
#include <apt-pkg/configuration.h>

#include "rapt-compat.h"
#include "crpmtag.h"
#include "cached_hash.h"
#include "genutil.h"

using namespace std;

raptTag tags[] =  {
       RPMTAG_NAME,
       RPMTAG_EPOCH,
       RPMTAG_VERSION,
       RPMTAG_RELEASE,
       RPMTAG_GROUP,
       RPMTAG_ARCH,
       RPMTAG_PACKAGER,
       RPMTAG_SIZE,

       RPMTAG_DESCRIPTION,
       RPMTAG_SUMMARY,
       /*RPMTAG_HEADERI18NTABLE*/ HEADER_I18NTABLE,

       RPMTAG_REQUIREFLAGS,
       RPMTAG_REQUIRENAME,
       RPMTAG_REQUIREVERSION
};
int numTags = sizeof(tags) / sizeof(tags[0]);

raptTag add_tags[] =  {
       CRPMTAG_FILENAME,
       CRPMTAG_FILESIZE,

       CRPMTAG_DIRECTORY,
       CRPMTAG_BINARY,
};
int numAddTags = sizeof(add_tags) / sizeof(add_tags[0]);

static
void readIndex(FILE *fp, map<string, vector<const char *> > &table)
{
   char line[512];
   while (fgets(line, sizeof(line), fp)) {
      line[strlen(line)-1] = '\0'; // trim newline
      char *val = strchr(line, ' ');
      assert(val);
      *val++ = '\0';
      const char *srpm = line;
      const char *rpm = strdup(val);
      assert(rpm);
      table[srpm].push_back(rpm);
   }
}

static
int read_list_line(ifstream &strm, char **srpm)
{
       string line;
       if (std::getline(strm, line)) {
	   *srpm = strdup(line.c_str());
	   return 1;
       }
       *srpm = NULL;
       return 0;
}

static
int processPackageFile(const char *rpm, FD_t outfd, string srpmdir, bool mapi,
		       struct HashCaches &caches, map<string,
		       vector<const char *> > srpm2rpms)
{
      struct stat sb;
      if (stat(rpm, &sb) < 0) {
	 cerr << "gensrclist: " << rpm << ": " << strerror(errno) << endl;
	 return 1;
      }

      Header h = readHeader(rpm);
      if (h == NULL) {
	 FD_t infd = Fopen(rpm, "r");
	 h = headerRead(infd, HEADER_MAGIC_YES);
	 Fclose(infd);
      }
      if (h == NULL) {
	 cerr << "gensrclist: " << rpm << ": cannot read package header" << endl;
	 return 1;
      }

      Header newHeader = headerNew();
      copyTags(h, newHeader, numTags, tags);
      addAptTags(newHeader, srpmdir.c_str(), rpm, sb.st_size);

      if (caches.md5) {
	 char md5[34];
	 if (!caches.md5->HashForFile(rpm, sb.st_mtime, md5)) {
	    cerr << "gensrclist: " << rpm << ": cannot calculate hash" << endl;
	    return 1;
	 }
	 headerPutString(newHeader, CRPMTAG_MD5, md5);
      }

      if (caches.blake2b) {
	 char blake2b[129];
	 if (!caches.blake2b->HashForFile(rpm, sb.st_mtime, blake2b)) {
	    cerr << "gensrclist: " << rpm << ": cannot calculate hash" << endl;
	    return 1;
	 }
	 headerPutString(newHeader, CRPMTAG_BLAKE2B, blake2b);
      }

      map<string, vector<const char *> >::const_iterator I = srpm2rpms.find(rpm);
      if (I != srpm2rpms.end()) {
	 const vector<const char *> &rpmv = I->second;
	 assert(rpmv.size() > 0);
	 headerPutStringArray(newHeader, CRPMTAG_BINARY, (const char **) &rpmv[0], rpmv.size());
	 headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
      }
      else if (!mapi) { // write anyway
	 headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
      }

      headerFree(newHeader);
      headerFree(h);
      return 0;
}

void usage()
{
   cerr << "gensrclist " << VERSION << endl;
   cerr << "usage: gensrclist [<options>] <dir> <suffix> <srpm index>" << endl;
   cerr << "options:" << endl;
   cerr << " --basedir <DIR> directory to write srclist to instead of <dir>/base/"<<endl;
//   cerr << " --mapi         ???????????????????" << endl;
   cerr << " --flat          use a flat directory structure, where RPMS and SRPMS"<<endl;
   cerr << "                 are in the same directory level"<<endl;
   cerr << " --progress      show a progress bar" << endl;
   cerr << " --blake2b       add package file BLAKE2b to pkglist" << endl;
   cerr << " --disable-hash-caching" << endl;
   cerr << "                 disable caching of package file hashes" << endl;
   cerr << " --cachedir=DIR  use a custom directory for package hashsum cache"<<endl;
}

int main(int argc, char ** argv)
{
   char cwd[PATH_MAX];
   string repo_dir; /* Path to save srclist to. */
   string srclist_path; /* Path to output srclist file  */
   string rel_srpmpath; /* Relative SRPMS.<comp> path to save in
			   srclist's CRPMTAG_DIRECTORY tag. */
   string srpmpath; /* SRPMS.<comp> path to find src.rpm packages. */
   struct dirent **dirEntries;
   int i;
   int entry_no = 0, entry_cur;
   bool mapi = false;
   bool progressBar = false;
   bool flatStructure = false;
   bool add_blake2b = false;
   bool hash_caching = true;

   char *arg_dir, *arg_comp, *arg_srpmindex;

   string basedir = "base";
   bool prevStdin = false;
   char *op_add_list = NULL;
   char *op_rm_list = NULL;

   putenv((char *)"LC_ALL="); // Is this necessary yet (after i18n was supported)?
   for (i = 1; i < argc; i++) {
      if (strcmp(argv[i], "--mapi") == 0) {
	 mapi = true;
      } else if (strcmp(argv[i], "--flat") == 0) {
	 flatStructure = true;
      } else if (strcmp(argv[i], "--progress") == 0) {
	 progressBar = true;
      } else if (strcmp(argv[i], "--blake2b") == 0) {
	 add_blake2b = true;
      } else if (strcmp(argv[i], "--no-hash-cache") == 0) {
	 hash_caching = false;
      } else if (strcmp(argv[i], "--cachedir") == 0) {
	 i++;
	 if (i < argc) {
            _config->Set("Dir::Cache", argv[i]);
	 } else {
            cerr << "genpkglist: argument missing for option --cachedir"<<endl;
	    exit(1);
	 }
      } else if (strcmp(argv[i], "--prev-stdin") == 0) {
	 prevStdin = true;
      } else if (strcmp(argv[i], "--add-list") == 0) {
	 i++;
	 if (i < argc) {
	    op_add_list = argv[i];
	 } else {
	    cerr << "gensrclist: filename missing for option --add-list"<<endl;
	    return 1;
	 }
      } else if (strcmp(argv[i], "--rm-list") == 0) {
	 i++;
	 if (i < argc) {
	    op_rm_list = argv[i];
	 } else {
	    cerr << "gensrclist: filename missing for option --rm-list"<<endl;
	    return 1;
	 }
      } else if (strcmp(argv[i], "--basedir") == 0) {
	 i++;
	 if (i < argc) {
	    basedir = argv[i];
	 } else {
	    cerr << "gensrclist: directory missing for option --basedir"<<endl;
	    return 1;
	 }
      } else {
	 break;
      }
   }
   if (argc - i == 3) {
      arg_dir = argv[i++];
      arg_comp = argv[i++];
      arg_srpmindex = argv[i++];
   }
   else {
      usage();
      exit(1);
   }

   if (!prevStdin && (op_add_list || op_rm_list)) {
      cerr << "genpkglist: --add-list and --rm-list don't make sense without --prev-stdin" << endl;
   }

   map<string, vector<const char *> > srpm2rpms;
   FILE *fp = fopen(arg_srpmindex, "r");
   if (fp == NULL) {
      cerr << "gensrclist: " << arg_srpmindex << ": " << strerror(errno) << endl;
      return 1;
   }
   readIndex(fp, srpm2rpms);
   fclose(fp);

   if(getcwd(cwd, PATH_MAX) == 0)
   {
      cerr << argv[0] << ": " << strerror(errno) << endl;
      exit(1);
   }

   if (*arg_dir != '/')
       repo_dir = string(cwd) + "/" + string(arg_dir);
   else
       repo_dir = string(arg_dir);

   rel_srpmpath = "SRPMS." + string(arg_comp);

   if (!flatStructure) {
      srpmpath = repo_dir + "/../SRPMS." + string(arg_comp);
      rel_srpmpath = "../" + rel_srpmpath;
   } else {
      srpmpath = repo_dir + "/SRPMS." + string(arg_comp);
#ifndef REMOVE_THIS_SOMEDAY
   /* This code is here just so that code in rpmsrcrecords.cc in versions
    * prior to 0.5.15cnc4 is able to detect if that's a "new" style SRPM
    * directory scheme, or an old style. Someday, when 0.5.15cnc4 will be
    * history, this code may be safely removed. */
      rel_srpmpath = "./" + rel_srpmpath;
#endif
   }

   FD_t prevfd = NULL;
   if (prevStdin) {
      prevfd = fdDup(0);
      if (Ferror(prevfd)) {
	cerr << "gensrclist: cannot open stdin" << endl;
	return 1;
      }
   }

   ifstream add_strm;
   if (op_add_list) {
       add_strm = ifstream(op_add_list);
       if (!add_strm) {
	  cerr << "gensrclist: cannot open " << op_add_list <<endl;
	  return 1;
       }
   }

   ifstream rm_strm;
   if (op_rm_list) {
       rm_strm = ifstream(op_rm_list);
       if (!rm_strm) {
	  cerr << "gensrclist: cannot open " << op_rm_list <<endl;
	  return 1;
       }
   }

   if (!prevStdin)
       entry_no = scandir(srpmpath.c_str(), &dirEntries, selectRPMs, alphasort);
   if (entry_no < 0) {
      cerr << "gensrclist: error opening directory " << srpmpath.c_str() << ": "
	  << strerror(errno) << endl;
      return 1;
   }

   if(chdir(srpmpath.c_str()) != 0)
   {
      cerr << argv[0] << ":" << strerror(errno) << endl;
      exit(1);
   }
   
   srclist_path = repo_dir + "/" + basedir + "/srclist." + string(arg_comp);
   
   FD_t outfd = Fopen(srclist_path.c_str(), "w+");
   if (!outfd) {
      cerr << "gensrclist: error creating file " << srclist_path.c_str() << ": "
	  << strerror(errno) << endl;
      return 1;
   }

   string CachePrefix;
   if (hash_caching)
      CachePrefix = string(arg_dir) + string(arg_comp);

   struct HashCaches caches = {};
   caches.md5 = new CachedHash(PGPHASHALGO_MD5, CachePrefix, "gensrclist", ".md5cache");

   if (add_blake2b)
       caches.blake2b = new CachedHash(PGPHASHALGO_BLAKE2B, CachePrefix, "gensrclist", ".blake2bcache");

   for (entry_cur = 0; entry_cur < entry_no; entry_cur++) {

      if (progressBar)
	 simpleProgress(entry_cur + 1, entry_no);

      const char *fname = dirEntries[entry_cur]->d_name;
      if (processPackageFile(fname, outfd, rel_srpmpath, mapi, caches, srpm2rpms))
	  return 1;
   }

   if (prevStdin) {
       Header h;
       const char * old_srpm = NULL;
       char * add_srpm;
       char * rm_srpm;
       int progress = 0;

       read_list_line(add_strm, &add_srpm);
       read_list_line(rm_strm, &rm_srpm);
       h = headerRead(prevfd, HEADER_MAGIC_YES);

       while (1) {
	   if (progressBar)
	       simpleProgress(progress, 0);

	   if (h == NULL && add_srpm == NULL && rm_srpm == NULL)
	       break;

	   if (h)
	       old_srpm = headerFormat(h, "%{name}-%{version}-%{release}.src.rpm", NULL);
	   if (h && old_srpm == NULL) {
	       cerr << "gensrclist: bad input from stdin (rpm)" << endl;
	       headerFree(h);
	       return 1;
	   }

	   if (rm_srpm != NULL) {
	       if (!old_srpm) {
		   cerr << "gensrclist: failed to remove " << string(rm_srpm) << " package" << endl;
		   return 1;
	       }
	       int c = strcmp(old_srpm, rm_srpm);
	       if (c == 0) {
		   read_list_line(rm_strm, &rm_srpm);
		   h = headerRead(prevfd, HEADER_MAGIC_YES);
		   continue;
	       } else if (c > 0) {
		   cerr << "gensrclist: failed to remove " << string(rm_srpm) << " package" << endl;
		   headerFree(h);
		   return 1;
	       }
	   }

	   if (add_srpm != NULL) {
	       int c = 1;
	       if (old_srpm)
		   c = strcmp(old_srpm, add_srpm);
	       if (c == 0) {
		   cerr << "gensrclist: unexpected header " << string(old_srpm) << " in package from stdin" << endl;
		   headerFree(h);
		   return 1;
	       }
	       else if (c > 0) {
		   if (processPackageFile(add_srpm, outfd, rel_srpmpath, mapi, caches, srpm2rpms))
		       return 1;
		   progress++;
		   read_list_line(add_strm, &add_srpm);
		   continue;
	       }
	   }

	   /*
	    * We do not really need to modify the header here, but rpm
	    * changes the whole header layout in headerRead function, so we
	    * have to copy all tags to restore the layout.
	    */
	   Header newHeader = headerNew();
	   copyTags(h, newHeader, numTags, tags);
	   copyTags(h, newHeader, numAddTags, add_tags);

	   if (!copyTag(h, newHeader, CRPMTAG_MD5)) {
	      cerr << "gensrclist: old srclist does not contain MD5 hash for " << string(old_srpm) << endl;
	   }

	   if (add_blake2b) {
	      if (!copyTag(h, newHeader, CRPMTAG_BLAKE2B)) {
		 cerr << "gensrclist: old srclist does not contain BLAKE2b hash for " << string(old_srpm) << endl;
		 return 1;
	      }
	   }

	   headerWrite(outfd, newHeader, HEADER_MAGIC_YES);
	   headerFree(newHeader);

	   progress++;
	   headerFree(h);
	   h = headerRead(prevfd, HEADER_MAGIC_YES);
       }
   }

   Fclose(outfd);

   delete caches.md5;
   delete caches.blake2b;

   return 0;
}

// vim:sts=3:sw=3
